/*
 *	Copyright (C) 2003-2006 Gabest
 *	http://www.gabest.org
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *  http://www.gnu.org/copyleft/gpl.html
 *
 */

#include "StdAfx.h"
#include "ccdecoder.h"

CCDecoder::CCDecoder(CString fn, CString rawfn) : m_fn(fn), m_rawfn(rawfn)
{
    m_sts.CreateDefaultStyle(ANSI_CHARSET);

    m_time = 0;
    m_fEndOfCaption = false;
    memset(m_buff, 0, sizeof(m_buff));
    memset(m_disp, 0, sizeof(m_disp));
    m_cursor = CPoint(0, 0);

    if(!m_rawfn.IsEmpty())
        _tremove(m_rawfn);
}

CCDecoder::~CCDecoder()
{
    if(!m_sts.IsEmpty() && !m_fn.IsEmpty())
    {
        m_sts.Sort();
        m_sts.SaveAs(m_fn, EXTSRT, -1, CTextFile::ASCII);
        m_sts.SaveAs(m_fn.Left(m_fn.ReverseFind('.') + 1) + _T("utf8.srt"), EXTSRT, -1, CTextFile::UTF8);
        m_sts.SaveAs(m_fn.Left(m_fn.ReverseFind('.') + 1) + _T("utf16le.srt"), EXTSRT, -1, CTextFile::LE16);
        m_sts.SaveAs(m_fn.Left(m_fn.ReverseFind('.') + 1) + _T("utf16be.srt"), EXTSRT, -1, CTextFile::BE16);
    }
}

void CCDecoder::MoveCursor(int x, int y)
{
    m_cursor = CPoint(x, y);
    if(m_cursor.x < 0) m_cursor.x = 0;
    if(m_cursor.y < 0) m_cursor.y = 0;
    if(m_cursor.x >= 32) m_cursor.x = 0, m_cursor.y++;
    if(m_cursor.y >= 16) m_cursor.y = 0;
}

void CCDecoder::OffsetCursor(int x, int y)
{
    MoveCursor(m_cursor.x + x, m_cursor.y + y);
}

void CCDecoder::PutChar(WCHAR c)
{
    m_buff[m_cursor.y][m_cursor.x] = c;
    OffsetCursor(1, 0);
}

void CCDecoder::SaveDisp(__int64 time)
{
    CStringW str;

    for(ptrdiff_t row = 0; row < 16; row++)
    {
        bool fNonEmptyRow = false;

        for(ptrdiff_t col = 0; col < 32; col++)
        {
            if(m_disp[row][col])
            {
                CStringW str2(&m_disp[row][col]);
                if(fNonEmptyRow) str += ' ';
                str += str2;
                col += str2.GetLength();
                fNonEmptyRow = true;
            }
        }

        if(fNonEmptyRow) str += '\n';
    }

    if(str.IsEmpty()) return;

    m_sts.Add(str, true, (int)m_time, (int)time);
}

void CCDecoder::DecodeCC(BYTE* buff, int len, __int64 time)
{
    if(!m_rawfn.IsEmpty())
    {
        if(FILE* f = _tfopen(m_rawfn, _T("at")))
        {
            _ftprintf(f, _T("%02d:%02d:%02d.%03d\n"),
                      (int)(time / 1000 / 60 / 60),
                      (int)((time / 1000 / 60) % 60),
                      (int)((time / 1000) % 60),
                      (int)(time % 1000));

            for(ptrdiff_t i = 0; i < len; i++)
            {
                _ftprintf(f, _T("%02x"), buff[i]);
                if(i < len - 1) _ftprintf(f, _T(" "));
                if(i > 0 && (i & 15) == 15) _ftprintf(f, _T("\n"));
            }
            if(len > 0) _ftprintf(f, _T("\n\n"));
            fclose(f);
        }
    }

    for(ptrdiff_t i = 0; i < len; i++)
    {
        BYTE c = buff[i] & 0x7f;
        if(c >= 0x20)
        {
            static WCHAR charmap[0x60] =
            {
                ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', 0xE1, '+', ',', '-', '.', '/',
                '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', 0x3F,
                '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
                'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', 0xE9, ']', 0xED, 0xF3,
                0xFA, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
                'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 0xE7, 0xF7, 'N', 'n', 0x3F
            };

            PutChar(charmap[c - 0x20]);
        }
        else if(buff[i] != 0x80 && i < len - 1)
        {
            // codes and special characters are supposed to be doubled
            if(i < len - 3 && buff[i] == buff[i+2] && buff[i+1] == buff[i+3])
                i += 2;

            c = buff[i+1] & 0x7f;
            if(buff[i] == 0x91 && c >= 0x20 && c < 0x30) // formating
            {
                // TODO
            }
            else if(buff[i] == 0x91 && c == 0x39) // transparent space
            {
                OffsetCursor(1, 0);
            }
            else if(buff[i] == 0x91 && c >= 0x30 && c < 0x40) // special characters
            {
                static WCHAR charmap[0x10] =
                {
                    0x00ae, // (r)egistered
                    0x00b0, // degree
                    0x00bd, // 1/2
                    0x00bf, // inverted question mark
                    0x2122, // trade mark
                    0x00a2, // cent
                    0x00a3, // pound
                    0x266a, // music
                    0x00e0, // a`
                    0x00ff, // transparent space, handled above
                    0x00e8, // e`
                    0x00e2, // a^
                    0x00ea, // e^
                    0x00ee, // i^
                    0x00f4, // o^
                    0x00fb, // u^
                };

                PutChar(charmap[c - 0x30]);
            }
            else if(buff[i] == 0x92 && c >= 0x20 && c < 0x40) // extended characters
            {
                static WCHAR charmap[0x20] =
                {
                    0x00c0, // A'
                    0x00c9, // E'
                    0x00d3, // O'
                    0x00da, // U'
                    0x00dc, // U:
                    0x00fc, // u:
                    0x2018, // `
                    0x00a1, // inverted !
                    0x002a, // *
                    0x2019, // '
                    0x002d, // -
                    0x00a9, // (c)opyright
                    0x2120, // SM
                    0x00b7, // . (dot in the middle)
                    0x201c, // inverted "
                    0x201d, // "

                    0x00c1, // A`
                    0x00c2, // A^
                    0x00c7, // C,
                    0x00c8, // E`
                    0x00ca, // E^
                    0x00cb, // E:
                    0x00eb, // e:
                    0x00ce, // I^
                    0x00cf, // I:
                    0x00ef, // i:
                    0x00d4, // O^
                    0x00d9, // U`
                    0x00f9, // u`
                    0x00db, // U^
                    0x00ab, // <<
                    0x00bb, // >>
                };

                PutChar(charmap[c - 0x20]);
            }
            else if(buff[i] == 0x13 && c >= 0x20 && c < 0x40) // more extended characters
            {
                static WCHAR charmap[0x20] =
                {
                    0x00c3, // A~
                    0x00e3, // a~
                    0x00cd, // I'
                    0x00cc, // I`
                    0x00ec, // i`
                    0x00d2, // O`
                    0x00f2, // o`
                    0x00d5, // O~
                    0x00f5, // o~
                    0x007b, // {
                    0x007d, // }
                    0x005c, // /* \ */
                    0x005e, // ^
                    0x005f, // _
                    0x00a6, // |
                    0x007e, // ~

                    0x00c4, // A:
                    0x00e4, // a:
                    0x00d6, // O:
                    0x00f6, // o:
                    0x00df, // B (ss in german)
                    0x00a5, // Y=
                    0x00a4, // ox
                    0x007c, // |
                    0x00c5, // Ao
                    0x00e5, // ao
                    0x00d8, // O/
                    0x00f8, // o/
                    0x250c, // |-
                    0x2510, // -|
                    0x2514, // |_
                    0x2518, // _|
                };

                PutChar(charmap[c - 0x20]);
            }
            else if(buff[i] == 0x94 && buff[i+1] == 0xae) // Erase Non-displayed [buffer] Memory
            {
                memset(m_buff, 0, sizeof(m_buff));
            }
            else if(buff[i] == 0x94 && buff[i+1] == 0x20) // Resume Caption Loading
            {
                memset(m_buff, 0, sizeof(m_buff));
            }
            else if(buff[i] == 0x94 && buff[i+1] == 0x2f) // End Of Caption
            {
                if(memcmp(m_disp, m_buff, sizeof(m_disp)) != 0)
                {
                    if(m_fEndOfCaption)
                        SaveDisp(time + (i / 2) * 1000 / 30);

                    m_fEndOfCaption = true;
                    memcpy(m_disp, m_buff, sizeof(m_disp));
                    m_time = time + (i / 2) * 1000 / 30;
                }
            }
            else if(buff[i] == 0x94 && buff[i+1] == 0x2c) // Erase Displayed Memory
            {
                if(m_fEndOfCaption)
                {
                    m_fEndOfCaption = false;
                    SaveDisp(time + (i / 2) * 1000 / 30);
                }

                memset(m_disp, 0, sizeof(m_disp));
            }
            else if(buff[i] == 0x97 && (buff[i+1] == 0xa1 || buff[i+1] == 0xa2 || buff[i+1] == 0x23)) // Tab Over
            {
                OffsetCursor(buff[i+1] & 3, 0);
            }
            else if(buff[i] == 0x91 || buff[i] == 0x92 || buff[i] == 0x15 || buff[i] == 0x16
                    || buff[i] == 0x97 || buff[i] == 0x10 || buff[i] == 0x13 || buff[i] == 0x94) // curpos, color, underline
            {
                int row = 0;
                switch(buff[i])
                {
                default:
                case 0x91:
                    row = 0;
                    break;
                case 0x92:
                    row = 2;
                    break;
                case 0x15:
                    row = 4;
                    break;
                case 0x16:
                    row = 6;
                    break;
                case 0x97:
                    row = 8;
                    break;
                case 0x10:
                    row = 10;
                    break;
                case 0x13:
                    row = 12;
                    break;
                case 0x94:
                    row = 14;
                    break;
                }
                if(buff[i+1] & 0x20) row++;

                int col = buff[i+1] & 0xe;
                if(col == 0 || (col > 0 && !(buff[i+1] & 0x10))) col = 0;
                else col <<= 1;

                MoveCursor(col, row);
            }
            else
            {
                int iiii = 0;
            }

            i++;
        }
    }
}

void CCDecoder::ExtractCC(BYTE* buff, int len, __int64 time)
{
    for(ptrdiff_t i = 0; i < len - 9; i++)
    {
        if(*(DWORD*)&buff[i] == 0xb2010000 && *(DWORD*)&buff[i+4] == 0xf8014343)
        {
            i += 8;
            int nBytes = buff[i++] & 0x3f;
            if(nBytes > 0)
            {
                nBytes = (nBytes + 1)&~1;

                BYTE* pData1 = new BYTE[nBytes];
                BYTE* pData2 = new BYTE[nBytes];

                if(pData1 && pData2)
                {
                    int nBytes1 = 0, nBytes2 = 0;

                    for(ptrdiff_t j = 0; j < nBytes && i < 0x800;)
                    {
                        if(buff[i++] == 0xff)
                        {
                            pData1[nBytes1++] = buff[i++];
                            pData1[nBytes1++] = buff[i++];
                        }
                        else i += 2;

                        j++;

                        if(j >= nBytes) break;

                        if(buff[i++] == 0xff)
                        {
                            pData2[nBytes2++] = buff[i++];
                            pData2[nBytes2++] = buff[i++];
                        }
                        else i += 2;

                        j++;
                    }

                    if(nBytes1 > 0)
                        DecodeCC(pData1, nBytes1, time);

                    if(nBytes2 > 0)
                        DecodeCC(pData2, nBytes2, time);
                }

                if(pData1) delete [] pData1;
                if(pData2) delete [] pData2;
            }

            break;
        }
    }
}
